package treeedit;

import java.awt.AWTEvent;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.concurrent.Exchanger;

import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import com.sun.org.apache.xpath.internal.compiler.Keywords;

public class CodeTree extends JTree implements KeyListener
{
	CodeModel codeModel;
	CodeEditor codeEditor;
	CodeRenderer codeRenderer;
	CopyPasteManager copyPasteManager = new CopyPasteManager();
	
	public CodeTree(CodeModel model, CodeRenderer renderer)
	{
		super();
		setEditable(true);
		codeModel = model;
		//codeModel.load("/home/obiwan/Programming/lisp/cw03/zad1.lsp");
		codeModel.createMock();
		setModel(codeModel);
		codeEditor = new CodeEditor();
		setCellEditor(codeEditor);	
		addKeyListener(this);
		codeRenderer = renderer;
		setCellRenderer(codeRenderer);
		setRootVisible(false);
		setToggleClickCount(1);
		getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		//setPreferredSize(new Dimension(1100,1100));
		
				
	}
	
	public CodeTree(CodeModel model)
	{
		this(model,new CodeRendererExtra());
	}
	
		
	public void expandAll()
	{
		//elistener.expandPaths();
		for(int i = 0 ;i < getRowCount();i++)
		{
			expandRow(i);
		}
		
	}
	
	public void collapseAll()
	{
		for(int i = 0 ;i < getRowCount();i++)
		{
			collapseRow(i);
		}
	}

	public void reactBeginEditingEmptyTree(AWTEvent k)
	{
		TreePath pp;
		CodeNode root = (CodeNode) codeModel.getRoot();
		CodeNode t1;
		if(root.getChildren().size() != 0) return ;
		root.add(t1 = new CodeNode(""));
		pp = (new TreePath(root)).pathByAddingChild(t1);
		if(TreeStateController.isValidPath(this, pp))
				startEditingAtPath((new TreePath(root)).pathByAddingChild(t1));
		return ;
	
	}
	
	public void reactInsertAndStartEditingBrotherNode(AWTEvent k)
	{
		Enumeration enumeration = TreeStateController.saveExpansionState(this);

		TreePath path = codeModel.addNewBrotherNode(this, getSelectionPath());
		TreeStateController.loadExpansionState(this,enumeration);
//		System.out.println("PATH " + path);
		
		if(TreeStateController.isValidPath(this, path))
			startEditingAtPath(path);
	}

	public void reactEditSelectedNode(AWTEvent k)
	{
		TreePath path = getSelectionPath();
		if(path == null || path.getPathCount() == 1) return ;
		
		CodeNode node = (CodeNode) path.getLastPathComponent();
		
		if(!CodeEditor.isNodeEditable(node)) return ;
		
		if(TreeStateController.isValidPath(this, getSelectionPath()))
			startEditingAtPath(getSelectionPath());
	}

	public void reactDeleteSelectedNode(AWTEvent k)
	{
		Enumeration enumeration = TreeStateController.saveExpansionState(this);

		TreePath path = getSelectionPath();
		if(path == null) return;

		CodeNode thisNode = (CodeNode)path.getLastPathComponent();
		if(thisNode.getParent() == null) return ;
		CodeNode up = thisNode.getParent().getPrevious(thisNode);
		CodeNode down = thisNode.getParent().getNext(thisNode);
		
		codeModel.deleteNode(this, getSelectionPath());
		TreeStateController.loadExpansionState(this,enumeration);
		
		
		
		if(down != null)
		{
			setSelectionPath(path.getParentPath().pathByAddingChild(down));
		}
		else if(up != null)
		{
			setSelectionPath(path.getParentPath().pathByAddingChild(up));
		}
		else
		{
			setSelectionPath(path.getParentPath());
		}
		
	
	}
	
	public void reactMarkedNode (AWTEvent k)
	{
		TreePath path = getSelectionPath();
		if(path == null) return;
		CodeNode node = (CodeNode) path.getLastPathComponent();
		copyPasteManager.markNode(node);
		Messanger.getInstance().setMessage("Marked...",3000);
		refresh();

	}
	
	public void reactInsertInto ( AWTEvent k)
	{
		TreePath path = getSelectionPath();
		if(path == null) return;
		CodeNode where = (CodeNode) path.getLastPathComponent();
		CodeNode what = copyPasteManager.getMarkedNode();
		if(what == null) return ;
		
		codeModel.moveInto(this, what, where);
		
		copyPasteManager.unMarkNode();
		refresh();
		
	}
	
	public void reactInsertBefore(AWTEvent k)
	{
		TreePath path = getSelectionPath();
		if(path == null) return;
		CodeNode where = (CodeNode) path.getLastPathComponent();
		CodeNode what = copyPasteManager.getMarkedNode();
		if(what == null) return ;
		
		codeModel.moveBefore(this, what, where);
		
		copyPasteManager.unMarkNode();
		refresh();
	}
	
	public void reactInsertAfter(AWTEvent k)
	{
		TreePath path = getSelectionPath();
		if(path == null) return;
		CodeNode where = (CodeNode) path.getLastPathComponent();
		CodeNode what = copyPasteManager.getMarkedNode();
		if(what == null) return ;
		
		codeModel.moveAfter(this, what, where);
		
		copyPasteManager.unMarkNode();
		refresh();

	}
	
	public void reactStringify(AWTEvent k)
	{
		TreePath path = getSelectionPath();
		if(path == null) return;
		
		CodeNode node = (CodeNode) path.getLastPathComponent();
		//Debug
		if(node == null) return ;
		if(!node.isString())
		{
			node.setCode(node.getFullRepresentation());
			node.getChildren().clear();
			node.setString(true);
		}
		else
		{
			CodeParser parser = new CodeParser();
			CodeNode newNode;
			try {
//				System.out.println("Here");
				newNode = parser.parse(node.getCode());
//				System.out.println(newNode.getCode());
				node.exchange(newNode.getChildren().get(0));
			//	TestParse.printTree(newNode.getChildren().get(0),4);
				//Nasty trick
				node.setString(false);
				TreePath sel = path.getParentPath().pathByAddingChild(newNode.getChildren().get(0));
	
				if(TreeStateController.isValidPath(this, sel))
				{
//					System.out.println("New path " + sel );
					setSelectionPath(sel);
				}
			} catch (ParseException e) {
				
			}
			
			
		}
		refresh();
	}
	
	public void reactExchange(AWTEvent k)
	{
		TreePath path = getSelectionPath();
		if(path == null) return;
		CodeNode where = (CodeNode) path.getLastPathComponent();
		CodeNode what = copyPasteManager.getMarkedNode();
		if(what == null) return ;
		
//		System.out.println("React exchanger");
		
//		CodeNode whereParent = where.getParent();
//		CodeNode whatParent = what.getParent();
		
		if(!where.swap(what)) return ;
		TreePath parentPath = path.getParentPath();
		if(TreeStateController.isValidPath(this, parentPath.pathByAddingChild(what)))
				setSelectionPath(parentPath.pathByAddingChild(what));
				
		copyPasteManager.unMarkNode();
		refresh();

	}
	
	public void reactParenthesisIn(AWTEvent k)
	{
		TreePath path = getSelectionPath();
		TreePath parentPath = path.getParentPath();
		if(path == null) return;
		CodeNode node = (CodeNode) path.getLastPathComponent();
		
		CodeNode newNode = new CodeNode("( )");
		
		node.exchange(newNode);
		
		newNode.add(node);
		if(TreeStateController.isValidPath(this,parentPath.pathByAddingChild(newNode)))
			setSelectionPath(parentPath.pathByAddingChild(newNode));
				
		copyPasteManager.unMarkNode();
		refresh();
	
	}
	
	public void reactParenthesisOut(AWTEvent k)
	{
		TreePath path = getSelectionPath();
		TreePath parentPath = path.getParentPath();
		if(path == null) return;
		CodeNode node = (CodeNode) path.getLastPathComponent();
		if(!node.isString() && node.getRepresentation().equals("( )"))
		{
			CodeNode parent = node.getParent();
			CodeNode first = null;
			if(node.getChildren().size() != 0 )first = node.getChildren().get(0);
			
			for(CodeNode c : node.getChildren())
			{
				parent.insertBefore(node,c) ;
			}
			
			parent.remove(node);
		
			if(first != null)
				setSelectionPath(parentPath.pathByAddingChild(first));
			else setSelectionPath(parentPath);
					
			copyPasteManager.unMarkNode();
			refresh();
		
		}
		
	}
	
	public void reactPaste(AWTEvent k)
	{	
		TreePath path = getSelectionPath();
		if(path == null) return;
		CodeNode where = (CodeNode) path.getLastPathComponent();
		CodeNode what = copyPasteManager.getMarkedNode();
		if(what == null) return ;

		CodeNode newNode =  what.clone();
//		System.out.println("HERE CLONE");
		newNode.setParent(null);
		where.getParent().insertAfter(where,newNode);
		
		copyPasteManager.unMarkNode();
		refresh();

	}

	public void reactEditNewBefore(AWTEvent k)
	{

		Enumeration enumeration = TreeStateController.saveExpansionState(this);

		TreePath path = codeModel.addNewBrotherNodeUp(this, getSelectionPath());
		TreeStateController.loadExpansionState(this,enumeration);
//		System.out.println("PATH " + path);
		if(TreeStateController.isValidPath(this,path))
			startEditingAtPath(path);
	}
	
	public void reactEditNewInside(AWTEvent k)
	{//
		
		
		Enumeration enumeration = TreeStateController.saveExpansionState(this);

		TreePath p = getSelectionPath();
		CodeNode c = (CodeNode)p.getLastPathComponent();
		
		if(c.getChildren().size() == 0 && !c.getCode().equals("( )")) return ;
		CodeNode newNode;
		TreePath path;
		if(!c.isFunction())
			path = codeModel.addNewChildNode(this, getSelectionPath());
		else
		{
			c.insertAtIndex(newNode = new CodeNode(""),1);
			path = getSelectionPath().pathByAddingChild(newNode);
			codeModel.valueForPathChanged(new TreePath(codeModel.getRoot()), "");
		}
		
		TreeStateController.loadExpansionState(this,enumeration);
		
//		System.out.println("PATH " + path);
		if(TreeStateController.isValidPath(this,path))
			startEditingAtPath(path);
	}
	
	public void reactQuote(AWTEvent k)
	{
		TreePath path = getSelectionPath();
		if(path == null) return;
		
		CodeNode node = (CodeNode) path.getLastPathComponent();
		//Debug
		if(node == null) return ;
		if(node.getCode().equals("( )"))
		{
			node.setCode("'( )");
		}
		else if (node.getCode().equals("'( )"))
		{
			node.setCode("( )");
		}
		else if(node.getChildren().size() == 0)
		{
			if(node.getCode().startsWith("'"))
			{
				node.setCode(node.getCode().substring(1));
			}
			else
			{
				node.setCode("'" + node.getCode());
			}
		}
	
		refresh();
	}
	
	public void keyPressed(KeyEvent k) 
	{
		//System.out.println("Pressed " + k.getKeyChar());
	
		//	Only for empty tree start
	
		if(((CodeNode)codeModel.getRoot()).getChildren().size() == 0)
		{
			char c =k.getKeyChar(); 
		
			if(c == '(')
				reactBeginEditingEmptyTree(k);

			Lisper.getInstance().checkDefuns((CodeNode)codeModel.getRoot());

		
			return ;
		}
			
		//Enumeration enumeration = TreeStateController.saveExpansionState(this);
	
		if(getSelectionPath() == null) return;
		
		if(k.getKeyChar() == KeyEvent.VK_SPACE)
		{
			reactInsertAndStartEditingBrotherNode(k);
			
		}
		else if(k.getKeyChar() == KeyEvent.VK_ENTER)
		{
			reactEditSelectedNode(k);
						
		}
		else if(k.getKeyChar() == KeyEvent.VK_DELETE || ((k.getKeyCode() == KeyEvent.VK_D) && k.isControlDown()))
		{
			reactDeleteSelectedNode(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_F1)
		{
			expandAll();
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_F2)
		{
			collapseAll();
			k.consume();
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_C && k.isControlDown())
		{
			reactMarkedNode(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_V && k.isControlDown())
		{ //Insert into
			reactInsertInto(k);
	
		}
		else if(k.getKeyCode() == KeyEvent.VK_B && k.isControlDown())
		{ //Insert Before
			reactInsertBefore(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_A && k.isControlDown())
		{ //Insert After
			reactInsertAfter(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_S && k.isControlDown())
		{
			reactStringify(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_X && k.isControlDown())
		{
			reactExchange(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_I && k.isControlDown())
		{
			reactParenthesisIn(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_O && k.isControlDown())
		{
			reactParenthesisOut(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_H && k.isControlDown())
		{
//			System.out.println("here");
			reactEditNewBefore(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_P && k.isControlDown())
		{
			reactPaste(k);
			
		}
		else if(k.getKeyCode() == KeyEvent.VK_E && k.isControlDown())
		{
			 reactEditNewInside(k);
		}
		else if(k.getKeyCode() == KeyEvent.VK_Q && k.isControlDown())
		{
			reactQuote(k);
		}
		
		Lisper.getInstance().checkDefuns((CodeNode)codeModel.getRoot());
//		System.out.println("end");
		
		
	}

	public void refresh()
	{
		TreePath path = getSelectionPath();
		Enumeration e = TreeStateController.saveExpansionState(this);
		codeModel.valueForPathChanged(new TreePath(codeModel.getRoot()),"");
		TreeStateController.loadExpansionState(this,e);
		if(TreeStateController.isValidPath(this, path))
				if(path != null) setSelectionPath(path);
	}
	
	public void keyReleased(KeyEvent k) {
	
		
	}

	public void keyTyped(KeyEvent k) {
		
		
	}

	private class CopyPasteManager 
	{
		private CodeNode markedNode = null;
		
		public void markNode(CodeNode node)
		{
			if(markedNode != null)
			{
				markedNode.setMarked(false);
			}
			markedNode = node;
			node.setMarked(true);
		}
		
		public void unMarkNode()
		{
			if(markedNode != null)
			{
				markedNode.setMarked(false);
				markedNode = null;
				
			}
		}
		
		public CodeNode getMarkedNode()
		{
			return markedNode;
		}
		
	
		
	}

	public void load(String file) throws IOException, ParseException
	{
		codeModel.load(file);
		codeModel.valueForPathChanged(new TreePath(codeModel.getRoot()), "");
		cancelEditing();

	}
	
	public void save(String file) throws IOException
	{
		codeModel.save(file);
		codeModel.valueForPathChanged(new TreePath(codeModel.getRoot()), "");
		cancelEditing();

	}
	
	public void clear()
	{
		codeModel.createMock();
		codeModel.valueForPathChanged(new TreePath(codeModel.getRoot()), "");
		cancelEditing();
	}
	
	public CodeEditor getEditor()
	{
		return codeEditor;
	}
	
}
